package comet

import (
	"github.com/Terry-Mao/goim/api/protocol"
	"github.com/Terry-Mao/goim/internal/comet/conf"
	"github.com/Terry-Mao/goim/internal/comet/errors"
	log "github.com/golang/glog"
)

// Ring ring proto buffer.
type Ring struct {
	// 环的尾指针，从这里拿数据
	rp uint64
	// 环的大小，值一定是 2^N
	num uint64
	// 环的大小减 1
	mask uint64

	// TODO split cacheline, many cpu cache line size is 64
	// pad [40]byte

	// write
	// 环的头指针，向这里追加数据
	wp uint64
	// 底层表示环的数组
	data []protocol.Proto
}

// NewRing new a ring buffer.
func NewRing(num int) *Ring {
	r := new(Ring)
	r.init(uint64(num))
	return r
}

// Init init ring.
func (r *Ring) Init(num int) {
	r.init(uint64(num))
}

func (r *Ring) init(num uint64) {
	// 会把 num 向上取一个最近的 2^N 数
	if num&(num-1) != 0 {
		for num&(num-1) != 0 { // 这里会把最高位的 1 后面所有位都变成零，
			num &= num - 1
		}
		num <<= 1
	}
	r.data = make([]protocol.Proto, num)
	r.num = num
	// 因为 num 是 2^N，所以 mask 所有二进制位都会是 1
	r.mask = r.num - 1
}

// 拿环的尾部元素，用于读取数据
func (r *Ring) Get() (proto *protocol.Proto, err error) {
	if r.rp == r.wp { // 头尾指针相等，表示环形数组为空
		return nil, errors.ErrRingEmpty
	}
	proto = &r.data[r.rp&r.mask]
	return
}

// 尾指针增加
func (r *Ring) GetAdv() {
	r.rp++
	if conf.Conf.Debug {
		log.Infof("ring rp: %d, idx: %d", r.rp, r.rp&r.mask)
	}
}

// 拿环的头部元素，用于写入数据
func (r *Ring) Set() (proto *protocol.Proto, err error) {
	if r.wp-r.rp >= r.num {
		return nil, errors.ErrRingFull
	}
	proto = &r.data[r.wp&r.mask]
	return
}

// 头指针增加
func (r *Ring) SetAdv() {
	r.wp++
	if conf.Conf.Debug {
		log.Infof("ring wp: %d, idx: %d", r.wp, r.wp&r.mask)
	}
}

// Reset reset ring.
func (r *Ring) Reset() {
	r.rp = 0
	r.wp = 0
	// prevent pad compiler optimization
	// r.pad = [40]byte{}
}
